1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
import CredentialsProvider from "next-auth/providers/credentials"
import { getOrCreateSAMLUser, validateSAMLUserData } from '@/lib/users/saml-service'
interface SAMLProviderOptions {
id: string
name: string
idp: {
sso_login_url: string
sso_logout_url: string
certificates: string[]
}
sp: {
entity_id: string
private_key: string
certificate: string
assert_endpoint: string
}
}
export function SAMLProvider(options: SAMLProviderOptions) {
return CredentialsProvider({
id: options.id,
name: options.name,
credentials: {
user: {
label: "User Data",
type: "text"
}
},
async authorize(credentials) {
try {
if (!credentials?.user) {
console.error('No user data provided')
return null
}
console.log('🔐 SAML Provider: Processing user data')
// 사용자 데이터 파싱 (UTF-8 처리 개선)
const userDataString = credentials.user
console.log('🔤 Raw user data string:', userDataString.substring(0, 200) + '...')
const userData = JSON.parse(userDataString)
// 파싱된 데이터의 UTF-8 확인
console.log('🔤 Parsed user data UTF-8 check:', {
name: userData.name,
nameLength: userData.name?.length,
charCodes: userData.name ? [...userData.name].map(c => c.charCodeAt(0)) : []
})
if (!userData.id || !userData.email) {
console.error('Invalid SAML user data:', userData)
return null
}
console.log('✅ SAML Provider: User authenticated successfully', {
id: userData.id,
email: userData.email,
name: userData.name
})
// 🔥 SAML 사용자 데이터 검증
const isValidData = await validateSAMLUserData(userData)
if (!isValidData) {
console.error('Invalid SAML user data structure:', userData)
return null
}
// 🔥 JIT (Just-In-Time) 사용자 생성 또는 조회
const dbUser = await getOrCreateSAMLUser({
email: userData.email,
name: userData.name,
// companyId: userData.companyId,
// techCompanyId: userData.techCompanyId,
// ! domain = evcp 이면 vendor가 갖는 companyId, techCompanyId는 null
companyId: undefined,
techCompanyId: undefined,
domain: userData.domain
})
if (!dbUser) {
console.error('Failed to get or create SAML user')
return null
}
// DB에서 가져온 실제 사용자 정보 반환
const userResult = {
id: String(dbUser.id), // DB의 실제 ID
name: dbUser.name, // DB의 실제 이름
email: dbUser.email, // DB의 실제 이메일
companyId: dbUser.companyId, // DB의 실제 회사 ID
techCompanyId: dbUser.techCompanyId, // DB의 실제 기술회사 ID
domain: dbUser.domain, // DB의 실제 도메인
imageUrl: dbUser.imageUrl, // DB의 실제 이미지 URL
}
console.log('✅ SAML Provider: Returning user data to NextAuth:', userResult)
return userResult
} catch (error) {
console.error('❌ SAML Provider: Authentication failed', error)
return null
}
}
})
}
// SAML 로그인 URL 생성 헬퍼 함수
export function getSAMLLoginUrl(options: SAMLProviderOptions): string {
const params = new URLSearchParams({
SAMLRequest: 'placeholder', // 실제로는 createAuthnRequest()로 생성
RelayState: options.sp.assert_endpoint,
})
return `${options.idp.sso_login_url}?${params.toString()}`
}
// SAML 설정 검증
export function validateSAMLOptions(options: SAMLProviderOptions): boolean {
const required = [
options.idp.sso_login_url,
options.sp.entity_id,
options.sp.assert_endpoint
]
return required.every(field => field && field.length > 0)
}
|